/*
 * Copyright 2010-2012 Susanta Tewari. <freecode4susant@users.sourceforge.net>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package gui.genomemap.view;

import fs.genomemap.FileSettings;

import genomemap.data.DataSource;
import genomemap.data.GDataSource;
import genomemap.data.JDataSource;
import genomemap.worker.GLikeliHood;
import genomemap.worker.JLikeliHood;
import genomemap.worker.LHood;
import genomemap.worker.PLikeliHood;
import genomemap.worker.ProgCurve;
import genomemap.worker.SimulatedAnnealing;
import genomemap.worker.Worker;

import gui.genomemap.event.event.WorkerInputEvent;
import gui.genomemap.event.event.WorkerInputListener;
import gui.genomemap.inputModel.AnnealIModel;
import gui.genomemap.inputModel.ComGLHoodIModel;
import gui.genomemap.inputModel.ComJLHoodIModel;
import gui.genomemap.inputModel.ComPLHoodIModel;
import gui.genomemap.inputModel.InputModel;
import gui.genomemap.inputModel.ProgCurveIModel;
import gui.genomemap.outputModel.AnnealOModel;
import gui.genomemap.outputModel.ComGLHoodOModel;
import gui.genomemap.outputModel.ComJLHoodOModel;
import gui.genomemap.outputModel.ComPLHoodOModel;
import gui.genomemap.outputModel.OutputModel;
import gui.genomemap.outputModel.ProgCurveOModel;
import gui.genomemap.report.ReportGenerator;

import main.genomemap.Preferences;

import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.SwingWorker;

/**
 * @author Susanta Tewari
 * @version 1.0
 * @created 09-Nov-2007 4:10:47 PM
 */
public class ViewManager extends Worker implements WorkerInputListener {

    /**
     * Holds value of property view.
     */
    private View view = null;

    /**
     * set of output models persisted for this worker.
     */
    private Collection<OutputModel> outputModelCollection = new HashSet<>();

    /**
     * Constructs ...
     *
     * @param view description
     */
    public ViewManager(View view) {

        this.view = view;

        loadOutputModels();
        view.getTree().loadData(outputModelCollection);
    }

    void loadOutputModels() {

        if (Preferences.useFileSettings()) {

            FileSettings fileSettings = FileSettings.load();

            if (view instanceof ComGLHoodView) {

                outputModelCollection =
                    fileSettings.loadOutputModelCollection(ComGLHoodOModel.class);
            }

            if (view instanceof ComPLHoodView) {

                outputModelCollection =
                    fileSettings.loadOutputModelCollection(ComPLHoodOModel.class);
            }

            if (view instanceof ComJLHoodView) {

                outputModelCollection =
                    fileSettings.loadOutputModelCollection(ComJLHoodOModel.class);
            }

            if (view instanceof AnnealView) {
                outputModelCollection = fileSettings.loadOutputModelCollection(AnnealOModel.class);
            }

            if (view instanceof ProgCurveView) {

                outputModelCollection =
                    fileSettings.loadOutputModelCollection(ProgCurveOModel.class);
            }
        }

        if (Preferences.useRDBSettings()) {}
    }

    void removeOutputModel(OutputModel outputModel) {

        int ch_id                          = outputModel.getInputModel().getLinkageGroup();
        Collection<OutputModel> collection = new HashSet<>();

        collection.add(outputModel);
        removeOutputModels(collection);
        view.getTree().expandChNode(ch_id);
    }

    public void removeOutputModels(Collection<? extends OutputModel> outputModels) {


        // collect the ch_ids of the models
        Set<Integer> chIdSet = new HashSet<>();
        Iterator itr         = outputModels.iterator();

        while (itr.hasNext()) {

            OutputModel outputModel = (OutputModel) itr.next();

            chIdSet.add(new Integer(outputModel.getInputModel().getLinkageGroup()));
        }


        // remove models
        outputModelCollection.removeAll(outputModels);


        // save data
        saveModels();


        // update tree: remove the nodes for the models
        view.getTree().loadData(outputModelCollection);


        // expand the ChNodes
        for (Integer chIdObj : chIdSet) {

            view.getTree().expandChNode(chIdObj.intValue());
        }
    }

    void addOutputModel(OutputModel outputModel) {

        outputModel.setEditable(false);
        outputModelCollection.add(outputModel);


        // save data
        saveModels();


        // update tree: add the node and expand it
        updateTree();


        // select the output model in the tree and show it in the output view
        view.getTree().setModelSelected(outputModel);
    }

    void updateTree() {
        view.getTree().loadData(outputModelCollection);
    }

    /**
     * select the output model in the tree and show it in the output view
     */
    void selectModelInTree(OutputModel outputModel) {
        view.getTree().setModelSelected(outputModel);
    }

    public void saveModels() {


        // @todo use generic settings object
        if (Preferences.useFileSettings()) {
            FileSettings.load().saveOutputModels(outputModelCollection);
        }

        if (Preferences.useRDBSettings()) {}
    }

    /**
     * checks if this input model was previously run(persisted). This method should
     * be run after the worker model has been loaded
     * @param inputModel
     */
    public boolean contains(InputModel inputModel) {
        return false;
    }

    /**
     * Getter for property view.
     * @return Value of property view.
     */
    View getView() {
        return this.view;
    }

    @Override
    public void doComGLHoodInput(final WorkerInputEvent event) {

        SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() {

            public Void doInBackground() {

                if (!(view instanceof ComGLHoodView)) {
                    return null;
                }


                // get the input model
                ComGLHoodIModel inputModel = (ComGLHoodIModel) event.getInputModel();


                // get the output model and set it to LHood
                ComGLHoodOModel outputModel = new ComGLHoodOModel();


                // get the output model from the output view, if any: for example in case of editing
                ComGLHoodOModel savedOutputModel =
                    (ComGLHoodOModel) getView().getOutputView().getOutputModel();
                boolean workDone = false;

                if (savedOutputModel != null) {

                    ComGLHoodIModel savedInputModel =
                        (ComGLHoodIModel) savedOutputModel.getInputModel();


                    // first appear conditions that would remove the old model and skip to the fresh computation
                    if (!savedInputModel.equals(inputModel)) {
                        workDone = true;
                    } else if (savedInputModel.getOptionalOperModel().isComputingSE()
                               != inputModel.getOptionalOperModel().isComputingSE()) {

                        savedInputModel.getOptionalOperModel().setComputingSE(
                            inputModel.getOptionalOperModel().isComputingSE());

                        if (savedInputModel.getOptionalOperModel().isComputingSE()) {
                            workDone = true;
                        }

                    } else if (savedInputModel.getOptionalOperModel().isComputingSE()
                               != inputModel.getOptionalOperModel().isComputingSE()) {

                        savedInputModel.getOptionalOperModel().setComputingSE(
                            inputModel.getOptionalOperModel().isComputingSE());

                        if (savedInputModel.getOptionalOperModel().isComputingSE()) {

                            workDone = false;    // by default false also


                            // @todo compute the P-value .note the gData is required
                            // save the savedOutputModel(it belongs in the outputModelCollection
                            // so save the entire collection and show it on the browser
                            saveModels();
                            updateTree();
                            selectModelInTree(savedOutputModel);

                            return null;
                        }
                    }
                }

                setShowProgress(true);

                try {

                    GLikeliHood gHood = new GLikeliHood(inputModel);

                    gHood.setParentWorker(ViewManager.this);
                    gHood.addWorkerOutputListener(ViewManager.this.getView().getOutputView());


                    // run likelihood
                    gHood.runGHood();
                    outputModel.setInputModel(inputModel);
                    outputModel.setLHood(gHood.getLogLikeliHood());
                    outputModel.setCProbInitial(gHood.getCProbInitial());
                    outputModel.setCProbEst(gHood.getCProbEst());
                    outputModel.setRecomArray(gHood.getRecom());
                    outputModel.setCellProbEst(gHood.getCellProbEst());


                    // attempt to compute the P-Value if desired
                    if (inputModel.getOptionalOperModel().isComputingPValue()) {

                        double pValue = 0.0;

                        pValue = gHood.getPValue();

                        outputModel.setPValue(pValue);
                        outputModel.setComputedPValue(true);
                    }


                    // attempt to compute the Standard errors if desired
                    if (inputModel.getOptionalOperModel().isComputingSE()) {

                        double[] se = null;

                        try {

                            se = gHood.getStdErrs();

                            outputModel.setComputedSE(true);
                            outputModel.setSeArray(se);


                            // outputModel.setSeArray();

                        } catch (Exception ex) {

                            Logger.getLogger(ViewManager.class.getName()).log(Level.SEVERE, null,
                                             ex);
                            outputModel.setComputedSE(false);
                        }
                    }


                    // create output
                    setStatusInfo("Creating Model Output");
                    outputModel.setLog(gHood.getIterationLog());
                    ReportGenerator.createModelStat(outputModel);
                    ReportGenerator.createFigure(outputModel, ComGLHoodOModel.FIG_GMAP);
                    setShowProgress(false);

                    if (workDone) {

                        setStatusInfo("Removing the Old Model");
                        ViewManager.this.removeOutputModel(savedOutputModel);
                    }


                    // save the output model and show it on the model browser
                    setStatusInfo("Adding the Output Model");
                    ViewManager.this.addOutputModel(outputModel);

                } catch (Exception exception) {
                    exception.printStackTrace();
                }

                setStatusInfo("Finished");

                return null;
            }
        };

        worker.execute();
    }

    @Override
    public void doComPLHoodInput(final WorkerInputEvent event) {

        SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() {

            @Override
            public Void doInBackground() {

                if (!(view instanceof ComPLHoodView)) {
                    return null;
                }


                // get the input model
                ComPLHoodIModel inputModel = (ComPLHoodIModel) event.getInputModel();


                // get the output model and set it to LHood
                ComPLHoodOModel outputModel = new ComPLHoodOModel();


                // get the output model from the output view, if any: for example in case of editing
                ComPLHoodOModel savedOutputModel =
                    (ComPLHoodOModel) getView().getOutputView().getOutputModel();
                boolean workDone = false;

                if (savedOutputModel != null) {

                    workDone = true;

                    ComPLHoodIModel savedInputModel =
                        (ComPLHoodIModel) savedOutputModel.getInputModel();


                    // first appear conditions that would remove the old model and skip to the fresh computation
                    if (!savedInputModel.equals(inputModel)) {
                        workDone = true;
                    }


                    // other optional operations would be set in here
                }

                setShowProgress(true);

                try {

                    PLikeliHood pHood = new PLikeliHood(inputModel);

                    pHood.setParentWorker(ViewManager.this);
                    outputModel.setInputModel(inputModel);
                    pHood.addWorkerOutputListener(ViewManager.this.getView().getOutputView());


                    // run likelihood
                    pHood.runPHood();


                    // create output
                    outputModel.setLHood(pHood.getLogLikeliHood());
                    outputModel.setProbeSpacing(pHood.getProbeSpacing());


//                  setStatusInfo("Creating Model Output");
//                  ReportGenerator.createFigure(outputModel, ComPLHoodOModel.FIG_PMAP);
//                  ReportGenerator.createFigure(outputModel, ComPLHoodOModel.FIG_FULLMAP);
                    setShowProgress(false);

                    if (workDone) {

                        setStatusInfo("Removing the Old Model");
                        ViewManager.this.removeOutputModel(savedOutputModel);
                    }


                    // save the output model and show it on the model browser
                    setStatusInfo("Adding the Output Model");
                    ViewManager.this.addOutputModel(outputModel);

                } catch (Exception exception) {
                    exception.printStackTrace();
                }

                setStatusInfo("Finished");

                return null;
            }
        };

        worker.execute();
    }

    @Override
    public void doComJLHoodInput(final WorkerInputEvent event) {

        SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() {

            public Void doInBackground() {

                if (!(view instanceof ComJLHoodView)) {
                    return null;
                }


                // get the input model
                ComJLHoodIModel inputModel = (ComJLHoodIModel) event.getInputModel();


                // get the output model and set it to LHood
                ComJLHoodOModel outputModel = new ComJLHoodOModel();


                // get the output model from the output view, if any: for example in case of editing
                ComJLHoodOModel savedOutputModel =
                    (ComJLHoodOModel) getView().getOutputView().getOutputModel();
                boolean workDone = false;

                if (savedOutputModel != null) {

                    workDone = true;

                    ComJLHoodIModel savedInputModel =
                        (ComJLHoodIModel) savedOutputModel.getInputModel();


                    // first appear conditions that would remove the old model and skip to the fresh computation
                    // @todo probably complicating...
                    if (!savedInputModel.equals(inputModel)) {
                        workDone = true;
                    }


                    // other optional operations would be set in here
                }

                setShowProgress(true);

                try {

                    JLikeliHood jHood = new JLikeliHood(inputModel);

                    jHood.setParentWorker(ViewManager.this);
                    outputModel.setInputModel(inputModel);
                    jHood.addWorkerOutputListener(ViewManager.this.getView().getOutputView());


                    // run likelihood
                    jHood.runJHood();
                    outputModel.setLHood(jHood.getLogLikeliHood());
                    outputModel.setGeneOrder(jHood.getJData().getGData().getOrder());
                    outputModel.setPLogLikeliHood(jHood.getPHood().getLogLikeliHood());
                    outputModel.setGLogLikeliHood(jHood.getGHood().getLogLikeliHood());
                    outputModel.setProbeSpacing(jHood.getPHood().getProbeSpacing());


                    // create output
//                  setStatusInfo("Creating Model Output");
//                  ReportGenerator.createFigure(outputModel, ComJLHoodOModel.FIG_IPMAP);
//                  ReportGenerator.createFigure(outputModel, ComJLHoodOModel.FIG_IGMAP);
//                  ReportGenerator.createFigure(outputModel, ComJLHoodOModel.FIG_IFULLMAP);
                    setShowProgress(false);

                    if (workDone) {

                        setStatusInfo("Removing the Old Model");
                        ViewManager.this.removeOutputModel(savedOutputModel);
                    }


                    // save the output model and show it on the model browser
                    setStatusInfo("Adding the Output Model");
                    ViewManager.this.addOutputModel(outputModel);

                } catch (Exception exception) {
                    exception.printStackTrace();
                }

                setStatusInfo("Finished");

                return null;
            }
        };

        worker.execute();
    }

    @Override
    public void doAnnealInput(final WorkerInputEvent event) {

        SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() {

            public Void doInBackground() {


                // get the input model
                AnnealIModel inputModel = (AnnealIModel) event.getInputModel();


                // get the output model and set it to LHood
                AnnealOModel outputModel = new AnnealOModel();


                // get the output model from the output view, if any: for example in case of editing
                AnnealOModel savedOutputModel =
                    (AnnealOModel) getView().getOutputView().getOutputModel();
                boolean workDone = false;

                if (savedOutputModel != null) {

                    workDone = true;

                    AnnealIModel savedInputModel = (AnnealIModel) savedOutputModel.getInputModel();


                    // first appear conditions that would remove the old model and skip to the fresh computation
                    if (!savedInputModel.equals(inputModel)) {
                        workDone = true;
                    }


                    // other optional operations would be set in here
                }

                setShowProgress(true);

                try {

                    SimulatedAnnealing annealing = new SimulatedAnnealing(inputModel);

                    annealing.setParentWorker(ViewManager.this);
                    outputModel.setInputModel(inputModel);

                    LHood lHood = null;

                    if (inputModel.getLhoodType() == LHood.GLHOOD) {

                        GLikeliHood gHood = new GLikeliHood();

                        gHood.setIterationLimit(inputModel.getGIterationLimit());
                        gHood.setConvgEpsilon(inputModel.getGConvgEpsilon());

                        GDataSource gData = DataSource.getGDataSource(inputModel.getLinkageGroup(),
                                                inputModel.getOrder());

                        gData.setRflpImputation(inputModel.getRflpImputation());
                        gHood.setGData(gData);

                        lHood = gHood;
                    }

                    if (inputModel.getLhoodType() == LHood.PLHOOD) {

                        lHood = new PLikeliHood();

                        lHood.setPData(DataSource.getPDataSource(inputModel.getLinkageGroup(),
                                inputModel.getOrder()));
                    }

                    if (inputModel.getLhoodType() == LHood.JLHOOD) {

                        JLikeliHood jHood = new JLikeliHood();

                        jHood.setGIterationLimit(inputModel.getGIterationLimit());


                        // @todo: later adjust in interface: for faster computation
                        jHood.setGIterationLimit(1);
                        jHood.setGConvgEpsilon(inputModel.getGConvgEpsilon());

                        JDataSource jData = DataSource.getJDataSource(inputModel.getLinkageGroup(),
                                                inputModel.getOrder());

                        jData.getGData().setRflpImputation(inputModel.getRflpImputation());
                        jHood.setJData(jData);

                        lHood = jHood;
                    }


                    // @todo add WorkerOutputListener
                    // annealing.addPropertyChangeListener(ViewManager.this.getView().getOutputView());
                    // run annealing
                    annealing.setAnnealable(lHood);
                    annealing.run();


                    // create output
                    setStatusInfo("Writing Model Output");
                    outputModel.setConvValue(annealing.getConvValue());
                    outputModel.setOrder(annealing.getConvOrder());
                    outputModel.setLog(annealing.getLogString());


//                  switch (inputModel.getLhoodType()) {
//                      case LHood.GLHOOD:
//                          setProgress(20);
//                          setStatusInfo("Creating the Genetic Map");
//                          ReportGenerator.createFigure(outputModel, AnnealOModel.FIG_GMAP);
//                          break;
//                      case LHood.PLHOOD:
//                          setProgress(10);
//                          setStatusInfo("Creating the Physical Map");
//                          ReportGenerator.createFigure(outputModel, AnnealOModel.FIG_PMAP);
//
//                          setProgress(60);
//                          setStatusInfo("Creating the Full Map");
//                          ReportGenerator.createFigure(outputModel, AnnealOModel.FIG_FULLMAP);
//                          break;
//                      case LHood.JLHOOD:
//                          setProgress(10);
//                          setStatusInfo("Creating the Integrated Genetic Map");
//                          ReportGenerator.createFigure(outputModel, AnnealOModel.FIG_IGMAP);
//
//                          setProgress(40);
//                          setStatusInfo("Creating the Integrated Physical Map");
//                          ReportGenerator.createFigure(outputModel, AnnealOModel.FIG_IPMAP);
//
//                          setProgress(70);
//                          setStatusInfo("Creating the Integrated Full Map");
//                          ReportGenerator.createFigure(outputModel, AnnealOModel.FIG_IFULLMAP);
//                          break;
//
//                  }
                    setProgress(100);

                    if (workDone) {

                        setStatusInfo("Removing the Old Model");
                        ViewManager.this.removeOutputModel(savedOutputModel);
                    }

                    setStatusInfo("Adding the new Output Model");
                    ViewManager.this.addOutputModel(outputModel);

                } catch (Exception exception) {
                    exception.printStackTrace();
                }

                setStatusInfo("Finished");
                setShowProgress(false);

                return null;
            }
        };

        worker.execute();
    }

    @Override
    public void doProgCurveInput(final WorkerInputEvent event) {

        SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() {

            public Void doInBackground() {

                if (!(view instanceof ProgCurveView)) {
                    return null;
                }


                // get the input model
                ProgCurveIModel inputModel = (ProgCurveIModel) event.getInputModel();


                // get the output model and set it to LHood
                ProgCurveOModel outputModel = new ProgCurveOModel();


                // get the output model from the output view, if any: for example in case of editing
                ProgCurveOModel savedOutputModel =
                    (ProgCurveOModel) getView().getOutputView().getOutputModel();
                boolean removeModel = false;

                if (savedOutputModel != null) {

                    removeModel = true;

                    ProgCurveIModel savedInputModel =
                        (ProgCurveIModel) savedOutputModel.getInputModel();


                    // first appear conditions that would remove the old model and skip to the fresh computation
                    if (!savedInputModel.equals(inputModel)) {
                        removeModel = true;
                    }


                    // other optional operations would be set in here
                }

                try {

                    setShowProgress(true);

                    ProgCurve progCurve = new ProgCurve(inputModel);

                    progCurve.setParentWorker(ViewManager.this);
                    outputModel.setInputModel(inputModel);


                    // @todo add WorkerOutputListener
                    // annealing.addPropertyChangeListener(ViewManager.this.getView().getOutputView());
                    // run likelihood
                    progCurve.run();
                    outputModel.setAnnealOModelSet(progCurve.getAnnealOModelSet());


                    // create the output
//                  setStatusInfo("Creating Model Output");
//                  ReportGenerator.createFigure(outputModel, ProgCurveOModel.FIG_PROG_CURVE);
                    setShowProgress(false);

                    if (removeModel) {

                        setStatusInfo("Removing the Old Model");
                        ViewManager.this.removeOutputModel(savedOutputModel);
                    }


                    // save the output model and show it on the model browser
                    setStatusInfo("Adding the Output Model");
                    ViewManager.this.addOutputModel(outputModel);
                    setStatusInfo("Finished");

                } catch (Exception exception) {
                    exception.printStackTrace();
                }

                return null;
            }
        };

        worker.execute();
    }

    public Collection<OutputModel> getOutputModelCollection() {
        return outputModelCollection;
    }

    public void setOutputModelCollection(Collection<OutputModel> outputModelCollection) {
        this.outputModelCollection = outputModelCollection;
    }
}
